﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.SolverFoundation.Common;
using Microsoft.SolverFoundation.Services;
using Microsoft.SolverFoundation.Solvers;

namespace SolverOptimizationAPI.Controllers
{
    public class OptimizationInfo
    {
        public double MinimumReturn { get; set; }
        public List<StockInfo> Stocks { get; set; }
    }

    public class StockInfo
    {
        public String Symbol { get; set; }
        public double MeanReturnRate { get; set; }
        public Dictionary<String, double> Covariances { get; set; }
    }

    public class OptimizedResults
    {
        public bool Feasible { get; set; }
        public bool Optimal { get; set; }
        public double ExpectedReturn { get; set; }
        public List<StockResult> Results { get; set; }
    }

    public class StockResult
    {
        public String Symbol { get; set; }
        public double Allocation { get; set; }
    }

    public class OptimizeController : ApiController
    {

        public OptimizedResults Post(OptimizationInfo info)
        {
            int m = info.Stocks.Count;

            InteriorPointSolver solver = new InteriorPointSolver();

            int[] allocations = new int[m];

            for (int invest = 0; invest < m; invest++)
            {
                string name = info.Stocks[invest].Symbol;
                solver.AddVariable(name, out allocations[invest]);
                solver.SetBounds(allocations[invest], 0, 1);
            }

            int expectedReturn;
            solver.AddRow("expectedReturn", out expectedReturn);

            solver.SetBounds(expectedReturn, info.MinimumReturn, double.PositiveInfinity);

            int unity;
            solver.AddRow("unity", out unity);
            solver.SetBounds(unity, 1, 1);

            for (int invest = m; 0 <= --invest; )
            {
                solver.SetCoefficient(expectedReturn, allocations[invest], info.Stocks[invest].MeanReturnRate);
                solver.SetCoefficient(unity, allocations[invest], 1);
            }

            int variance;
            solver.AddRow("variance", out variance);
            for (int invest = m; 0 <= --invest; )
            {
                for (int jnvest = m; 0 <= --jnvest; )
                {
                    solver.SetCoefficient(variance, info.Stocks[invest].Covariances[info.Stocks[jnvest].Symbol],
                        allocations[invest], allocations[jnvest]);
                }
            }

            solver.AddGoal(variance, 0, true);

            var ipParams = new InteriorPointSolverParams();

            solver.Solve(ipParams);

            var output = new OptimizedResults();

            output.Optimal = solver.Result == LinearResult.Optimal;
            output.Feasible = output.Optimal || solver.Result == LinearResult.Feasible;
            
            if (output.Feasible)
            {
                output.Feasible = true;

                double eReturn = 0;

                List<StockResult> results = new List<StockResult>();

                for (int i = 0; i < m; i++)
                {
                    var result = new StockResult();
                    result.Symbol = info.Stocks[i].Symbol;
                    result.Allocation = (double)solver.GetValue(allocations[i]);
                    eReturn += result.Allocation * info.Stocks[i].MeanReturnRate;
                    results.Add(result);
                }

                output.ExpectedReturn = eReturn;
                //output.ExpectedReturn = (double)solver.GetValue(expectedReturn);
                output.Results = results;
            }

            return output;
        }
    }
}
